Skip to main content

Overview

The Profile Management system allows authenticated users to view and update their personal information, upload custom avatars, and manage their account settings. All profile operations are secured and restricted to the authenticated user.

User Profile Model

The user profile is based on Django’s AbstractUser with custom extensions (models.py:5-16):
class Users(AbstractUser):
    email = models.EmailField(max_length=200, unique=True)
    first_name = models.CharField(max_length=200, blank=True, default='')
    last_name = models.CharField(max_length=200, blank=True, default='')
    number_phone = models.CharField(max_length=10, blank=True, null=True)
    avatar = models.ImageField(upload_to='avatars/', blank=True, null=True)
    
    REQUIRED_FIELDS = ['email']
    
    def __str__(self):
        return f"{self.id} user: {self.username} full name: {self.first_name} {self.last_name}"

Basic Info

Username, email, first name, and last name

Contact

Optional phone number field (max 10 characters)

Avatar

Profile picture uploaded to ‘avatars/’ directory
The email field is unique across all users and serves as a primary identifier for authentication.

Viewing Profile Information

Authenticated users can retrieve their complete profile data.

API Endpoint

URL: POST /api/users/profile/ Authentication: Required (IsAuthenticated) Response:
{
  "id": 1,
  "username": "johndoe",
  "email": "john@example.com",
  "first_name": "John",
  "last_name": "Doe",
  "number_phone": "1234567890",
  "avatar": "/media/avatars/profile_123.jpg"
}

Implementation

The profile view endpoint (views.py:82-92):
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def profile(request):
    try:
        serializer = UsersSerializer(instance=request.user)
        return Response(serializer.data, status=status.HTTP_200_OK)
    
    except Users.DoesNotExist:
        return Response({'error': 'The user cannot be found in the database.'}, status=status.HTTP_404_NOT_FOUND)
    except Exception as e:
        return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
The endpoint automatically uses request.user to retrieve the authenticated user’s profile, ensuring users can only access their own data.

Updating Profile Information

Users can update their profile details including personal information and avatar.

API Endpoint

URL: PUT /api/users/update-profile/<pk>/ Authentication: Required (IsAuthenticated) Content-Type: multipart/form-data (for avatar uploads) Request Body:
{
  "first_name": "Jonathan",
  "last_name": "Doe",
  "number_phone": "0987654321"
}
With Avatar:
curl -X PUT http://localhost:8000/api/users/update-profile/1/ \
  -H "Authorization: Bearer <token>" \
  -F "first_name=Jonathan" \
  -F "avatar=@profile.jpg"
Response:
{
  "id": 1,
  "username": "johndoe",
  "email": "john@example.com",
  "first_name": "Jonathan",
  "last_name": "Doe",
  "number_phone": "0987654321",
  "avatar": "/media/avatars/profile_123.jpg"
}

Implementation

The update profile endpoint (views.py:95-117):
@api_view(['PUT'])
@permission_classes([IsAuthenticated])
def update_profile(request, pk):
    try:
        user = get_object_or_404(Users, pk=pk)
        if user != request.user:
            return Response({"error": "Not authorized"}, status=status.HTTP_403_FORBIDDEN)
        
        parser_classes = (MultiPartParser, FormParser)
        
        if 'avatars' in request.FILES:
            avatar = request.FILES['avatar']
            user.avatar = avatar
            user.save()
        
        serializer = UsersSerializer(user, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
    except Exception as e:
        return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
The endpoint includes authorization checks to ensure users can only update their own profiles: if user != request.user: return Response({"error": "Not authorized"}, ...)

Partial Updates

The endpoint supports partial updates (partial=True), allowing users to update specific fields without sending the entire profile:
serializer = UsersSerializer(user, data=request.data, partial=True)
This means you can update just the phone number:
{
  "number_phone": "5551234567"
}
Or just the avatar:
curl -X PUT http://localhost:8000/api/users/update-profile/1/ \
  -F "avatar=@new_profile.jpg"

Avatar Support

The system supports profile picture uploads with specific handling.

Avatar Upload Process

1

File Selection

User selects an image file through the frontend upload component
2

Multipart Upload

File is sent as multipart/form-data in the PUT request
3

Server Processing

Backend receives file via request.FILES['avatar']
4

Storage

Image is saved to avatars/ directory in media storage
5

URL Generation

Django generates a URL path for accessing the uploaded image

Avatar Upload Component

The frontend includes a dedicated avatar uploader (AvatarUploader.jsx):
export default function AvatarUploader({ currentAvatar, onAvatarChange }) {
  const handleFileChange = (e) => {
    const file = e.target.files[0];
    if (file) {
      onAvatarChange(file);
      // Preview the selected image
      const reader = new FileReader();
      reader.onloadend = () => {
        setPreview(reader.result);
      };
      reader.readAsDataURL(file);
    }
  };
  
  return (
    <div>
      <img src={preview || currentAvatar} alt="Profile" />
      <input 
        type="file" 
        accept="image/*" 
        onChange={handleFileChange} 
      />
    </div>
  );
}

Supported Image Formats

The ImageField accepts common image formats:
  • JPEG (.jpg, .jpeg)
  • PNG (.png)
  • GIF (.gif)
  • WebP (.webp)
  • BMP (.bmp)
For optimal performance, consider implementing client-side image resizing before upload to reduce bandwidth and storage requirements.

Account Deletion

Users can permanently delete their accounts and all associated data.

API Endpoint

URL: DELETE /api/users/delete/<user_id>/ Authentication: Required (IsAuthenticated) Response:
{
  "message": "User deleted successfully"
}

Implementation

The delete user endpoint (views.py:121-129):
@api_view(['DELETE'])
@permission_classes([IsAuthenticated])
def delete_user(request, user_id):
    try:
        user = get_object_or_404(Users, id=user_id)
        user.delete()
        return Response({"message": "User deleted successfully"}, status=status.HTTP_204_NO_CONTENT)
    except Exception as e:
        return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
Cascade Deletion: When a user is deleted, all related data is automatically removed due to the on_delete=models.CASCADE setting on foreign keys. This includes:
  • All saved passwords
  • Uploaded avatar
  • Any other user-related data
This action is irreversible.

Confirmation Flow

Best practice is to implement a confirmation dialog:
const handleDeleteAccount = async () => {
  const confirmed = window.confirm(
    "Are you sure you want to delete your account? This action cannot be undone."
  );
  
  if (confirmed) {
    try {
      await deleteUser(userId);
      sessionStorage.clear();
      window.location.href = "/auth/sign-up";
    } catch (error) {
      console.error("Error deleting account:", error);
    }
  }
};

Profile Features Summary

View Profile

Retrieve complete profile information including avatar URL

Update Info

Modify personal details with partial update support

Upload Avatar

Upload and update profile pictures with multipart support

Delete Account

Permanently remove account and all associated data

Frontend Profile Page

The profile page is implemented with a content component (Content.jsx) that displays and manages profile information:
export default function ProfileContent() {
  const [user, setUser] = useState(null);
  const [isEditing, setIsEditing] = useState(false);
  
  useEffect(() => {
    const fetchProfile = async () => {
      const userData = JSON.parse(sessionStorage.getItem('user'));
      setUser(userData);
    };
    fetchProfile();
  }, []);
  
  return (
    <div>
      <AvatarUploader currentAvatar={user?.avatar} />
      <div>
        <p>Username: {user?.username}</p>
        <p>Email: {user?.email}</p>
        <p>Name: {user?.first_name} {user?.last_name}</p>
        <p>Phone: {user?.number_phone}</p>
      </div>
      <button onClick={() => setIsEditing(true)}>Edit Profile</button>
    </div>
  );
}

Authorization Model

Profile management includes multiple layers of authorization:

Endpoint-Level Authorization

@permission_classes([IsAuthenticated])
Requires valid JWT token in Authorization header.

User-Level Authorization

if user != request.user:
    return Response({"error": "Not authorized"}, status=status.HTTP_403_FORBIDDEN)
Ensures users can only modify their own profiles.

Automatic User Filtering

serializer = UsersSerializer(instance=request.user)
Automatically uses the authenticated user from the request.

Error Handling

Error ScenarioHTTP StatusResponse
User not found404{"error": "The user cannot be found in the database."}
Unauthorized access403{"error": "Not authorized"}
Validation error400{...serializer.errors}
Server error500{"error": "<error message>"}

Media Configuration

For avatar uploads to work properly, ensure your Django settings include:
settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
And in your URLs configuration:
urls.py
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    # ... your url patterns
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
In production, serve media files through a CDN or object storage service like AWS S3 rather than directly from Django.

Best Practices

Input Validation

Validate email format, phone number length, and image file types before submission

Image Optimization

Resize images client-side to reduce upload time and storage costs

Error Feedback

Display clear, actionable error messages for validation failures

Confirmation Dialogs

Require confirmation for destructive actions like account deletion
Implement optimistic UI updates for better user experience: update the UI immediately and roll back if the API call fails.